
#include "lua_log.hpp"
#include "lua_network.hpp"
//#include "lua_serial.hpp"
#include "lua_filewatcher.hpp"

#include "bLua.h"
#include "lua.hpp"

#include <ghc/filesystem.hpp>
namespace fs = ghc::filesystem;

lua_State* L;
std::mutex L_mtx;

t_http_r http_lua_call(const char* fname,
                       const char* method,
                       const char* path,
                       const char* body,
                       map_ss      headers,
                       map_ss      params)
{
    std::lock_guard<std::mutex> locker(L_mtx);

    int         status;
    std::string content_type, respBody;

    auto err = bLua::call_lua_global_func(L, fname, std::tie(status, content_type, respBody), method, path, body,
                                          headers, params);

    if (err)
    {
        spdlog::error("{} ret error. {}\n", fname, err.value().c_str());
    }

    return std::tie(status, content_type, respBody);
}

void mqtt_lua_call(const char* fname, const char* arg1, const char* arg2)
{
    std::lock_guard<std::mutex> locker(L_mtx);

    auto err = bLua::call_lua_global_func(L, fname, std::tie(), arg1, arg2);
    if (err)
    {
        spdlog::error("{} ret error. {}\n", fname, err.value().c_str());
    }
}

int seri_lua_call(const char* fname, const char* data)
{
    std::lock_guard<std::mutex> locker(L_mtx);

    int offset = -1;

    auto err = bLua::call_lua_global_func(L, fname, std::tie(offset), data);
    if (err)
    {
        spdlog::error("{} ret error. {}\n", fname, err.value().c_str());
    }

    return offset;
}

static void msleep(int ms)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}


#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
std::string get_executable_dir()
{
    char buffer[PATH_MAX + 1] = {0};

#if defined(_MSC_VER)
    if (GetModuleFileNameA(NULL, buffer, MAX_PATH) == 0)
    {
        throw std::runtime_error("Failed to get executable path on Windows");
    }
#else
    ssize_t count = readlink("/proc/self/exe", buffer, PATH_MAX);
    if (count <= 0)
    {
        throw std::runtime_error("Failed to read /proc/self/exe on Linux");
    }
    buffer[count] = '\0';
#endif

    fs::path full_path(buffer);
    if (!fs::exists(full_path))
    {
        throw std::runtime_error("Executable path does not exist");
    }

    char sep[2]{0};
    sep[0] = fs::path_helper_base<char>::preferred_separator;

    return full_path.parent_path().string().append(sep);
}

int lua_main(int argc, char* argv[])
{
    fs::path full_path(argv[0]);
    full_path.replace_extension(".lua");
    if (!fs::exists(full_path))
    {
        printf("%s does not exist", full_path.string().c_str());
    }

    std::string f = full_path.string();

    log_init();

    L = luaL_newstate();
    if (L == NULL)
    {
        printf("memory allocation error.");
        return -1;
    }

    lua_newtable(L);
    for (int i = 0; i < argc; i++)
    {
        lua_pushinteger(L, i);
        lua_pushstring(L, argv[i]);
        lua_settable(L, -3);
    }
    lua_setglobal(L, "args");

    luaL_openlibs(L);

    if (luaL_loadfile(L, f.c_str()))
    {
        const char* errmsg = lua_tostring(L, -1);
        printf("load lua script error.\n > %s", errmsg ? errmsg : "");
        return -1;
    }

    {
        bLua::reg_global_func(L, "msleep", msleep);

        bLua::reg_global_func(L, "newLog", newLog);
        bLua::reg_class<Log>(L);
        bLua::reg_class_func(L, "info", &Log::info);
        bLua::reg_class_func(L, "debug", &Log::debug);
        bLua::reg_class_func(L, "error", &Log::error);
        bLua::reg_class_func(L, "warn", &Log::warn);
        bLua::reg_class_func(L, "get_version", &Log::get_version);
        bLua::reg_class_func(L, "set_file", &Log::set_file);
        bLua::reg_class_func(L, "set_level", &Log::set_level);
        bLua::reg_class_func(L, "set_pattern", &Log::set_pattern);

        bLua::reg_global_func(L, "newMQTT", newMQTT);
        bLua::reg_class<MQTT>(L);
        bLua::reg_class_func(L, "open", &MQTT::open);
        bLua::reg_class_func(L, "subscribe", &MQTT::subscribe);
        bLua::reg_class_func(L, "publish", &MQTT::publish);

        bLua::reg_global_func(L, "newFileWatcher", newFileWatcher);
        bLua::reg_class<FileWatcher>(L);
    }

    if (lua_pcall(L, 0, 0, 0))
    {
        const char* errmsg = lua_tostring(L, -1);
        printf("pcall error. %s", errmsg ? errmsg : "");
        return -1;
    }

    while (true)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    return 0;
}
